﻿using Dapper;
using FI.Dapper.Interfaces;
using FI.Dapper.Transaction;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
using static Dapper.SqlMapper;

namespace FI.Dapper
{
    public class Repository : IRepository, IDisposable
    {

        private IDapperContext DapperContext { get; set; }
        public IDbTransaction Transaction { get; private set; }
        public IDbConnection Connection { get; }

        public bool? Buffered { get; }

        public int? Timeout { get; set; }
      

        public Repository(IDapperContext context)
        {
            var outerContext = CallContext.GetData("DapperContext") as IDapperContext;

            if (outerContext == null)
            {
                this.DapperContext = context;
                Transaction = null;
                Connection = context.Connection;
            }
            else
            {
                this.DapperContext = outerContext;
                Transaction = outerContext.Transaction;
                Connection = outerContext.Connection;
            }
        }
        public GridReader QueryMultiple(string sql, object param = null, int? commandTimeout = null, CommandType text = CommandType.Text)
        {
            return Connection.QueryMultiple(sql, param, Transaction, Timeout != null ? Timeout.Value : commandTimeout, text);
        }
        public Task<GridReader> QueryMultipleAsync(string sql, object param = null, int? commandTimeout = null, CommandType text = CommandType.Text)
        {
            return Connection.QueryMultipleAsync(sql, param, Transaction, Timeout != null ? Timeout.Value : commandTimeout, text);
        }
        public int Execute(string sql, object param = null, int? commandTimeout = null, CommandType text = CommandType.Text)
        {
            return Connection.Execute(sql, param, Transaction, Timeout != null ? Timeout.Value : commandTimeout, text); ;
        }
        public Task<int> ExecuteAsync(string sql, object param = null, int? commandTimeout = null, CommandType text = CommandType.Text)
        {
            return Connection.ExecuteAsync(sql, param, Transaction, Timeout != null ? Timeout.Value : commandTimeout, text);
        }
        public T ExecuteScalar<T>(string sql, object param = null, int? commandTimeout = null, CommandType text = CommandType.Text)
        {
            return Connection.ExecuteScalar<T>(sql, param, Transaction, Timeout != null ? Timeout.Value : commandTimeout, text);
        }
        public Task<T> ExecuteScalarAsync<T>(string sql, object param = null, int? commandTimeout = null, CommandType text = CommandType.Text)
        {
            return Connection.ExecuteScalarAsync<T>(sql, param, Transaction, Timeout != null ? Timeout.Value : commandTimeout, text);
        }

        public virtual int ExecuteProc(string procedureName, DynamicParameters dynamicParameters, int? commandTimeout = null)
        {
            if (string.IsNullOrEmpty(procedureName))
            {
                throw new ArgumentNullException("procedureName", "procedureName is empty");
            }

            return Connection.Execute(sql: procedureName, param: dynamicParameters,
                transaction: this.Transaction, commandTimeout: commandTimeout, commandType: CommandType.StoredProcedure);

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="sql"></param>
        /// <param name="dynamicParameters"></param>
        /// <param name="commandTimeout"></param>
        /// <returns></returns>
        public virtual int ExecuteSQL(string sql, DynamicParameters dynamicParameters, int? commandTimeout = null)
        {
            if (string.IsNullOrEmpty(sql))
            {
                throw new ArgumentNullException("sql", "sql is empty");
            }

            return Connection.Execute(sql: sql, param: dynamicParameters,
                transaction: this.Transaction, commandTimeout: commandTimeout, commandType: CommandType.Text);

        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="sql"></param>
        /// <param name="dynamicParameters"></param>
        /// <param name="commandTimeout"></param>
        /// <returns></returns>
        public virtual T ExecuteScalerBySQL<T>(string sql, DynamicParameters dynamicParameters, int? commandTimeout = null)
        {

            T local = Connection.ExecuteScalar<T>(sql, dynamicParameters, this.Transaction, commandTimeout: commandTimeout, commandType: CommandType.Text);
            return local;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="procedureName"></param>
        /// <param name="dynamicParameters"></param>
        /// <param name="commandTimeout"></param>
        /// <returns></returns>
        public virtual T GetByProc<T>(string procedureName, DynamicParameters dynamicParameters, int? commandTimeout = null) where T : class
        {
            if (string.IsNullOrEmpty(procedureName))
            {
                throw new ArgumentNullException("procedureName", "procedureName is empty");
            }
            return Connection.Query<T>(procedureName, dynamicParameters, this.Transaction,
                commandType: CommandType.StoredProcedure, commandTimeout: commandTimeout).FirstOrDefault();
        }
        public virtual T GetBySQL<T>(string sql, DynamicParameters dynamicParameters, int? commandTimeout = null) where T : class
        {
            if (string.IsNullOrEmpty(sql))
            {
                throw new ArgumentNullException("sql", "sql is empty");
            }
            return Connection.Query<T>(sql, dynamicParameters, this.Transaction,
                commandType: CommandType.Text, commandTimeout: commandTimeout).FirstOrDefault();
        }

        public virtual IEnumerable<T> GetListByProc<T>(string procedureName, DynamicParameters dynamicParameters, int? commandTimeout = null) where T : class
        {
            if (string.IsNullOrEmpty(procedureName))
            {
                throw new ArgumentNullException("procedureName", "procedureName is empty");
            }
            return Connection.Query<T>(procedureName, dynamicParameters, this.Transaction,
                commandType: CommandType.StoredProcedure, commandTimeout: commandTimeout);
        }
        public virtual IEnumerable<T> GetListBySQL<T>(string sql, DynamicParameters dynamicParameters, int? commandTimeout = null) where T : class
        {
            if (string.IsNullOrEmpty(sql))
            {
                throw new ArgumentNullException("sql", "sql is empty");
            }
            return Connection.Query<T>(sql, dynamicParameters, this.Transaction,
           commandType: CommandType.Text, commandTimeout: commandTimeout);
        }



        public IEnumerable<T> Query<T>(string sql, object param = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
        {
            return Connection.Query<T>(sql, param, Transaction, Buffered != null ? Buffered.Value : buffered, Timeout != null ? Timeout.Value : commandTimeout, commandType);
        }
        public Task<IEnumerable<T>> QueryAsync<T>(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
        {
            return Connection.QueryAsync<T>(sql, param, Transaction, Timeout != null ? Timeout.Value : commandTimeout, commandType);
        }
        public IEnumerable<dynamic> Query(string sql, object param = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
        {
            return Connection.Query(sql, param, Transaction, Buffered != null ? Buffered.Value : buffered, Timeout != null ? Timeout.Value : commandTimeout, commandType);
        }
        public Task<IEnumerable<dynamic>> QueryAsync(string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
        {
            return Connection.QueryAsync(sql, param, Transaction, Timeout != null ? Timeout.Value : commandTimeout, commandType);
        }

        public void Dispose()
        {
            Connection?.Close();
        }

      
    }
}
